/* @flow */

import {isDef, isObject} from 'shared/util'

/**
 * 该方法用于获得参数vnode中的静态class以及动态class，并将两种class拼接成一个字符串
 *
 * @param vnode
 * @returns {string}
 */
export function genClassForVnode(vnode: VNode): string {
  let data = vnode.data
  let parentNode = vnode
  let childNode = vnode

  //下面这两个while循环的含义：
  //比如，存在如下实例：
  //组件one、two、three，two组件是one组件的模板的直接元素，如下：'<two class="hello2" :class="hello22"></two>'，
  //同理，three是two组件的模板的直接元素，如下：'<three class="hello3" :class="hello33"></three>'。比如现在更新的是two组件，
  //则需要取得one、two和three组件中的静态与动态class，因为这三个组件的elm元素指向同一个dom元素，即three组件创建的根dom元素。所以这三个
  //组件中的所有class属性（无论是静态的还是动态的）都需要设置到该elm元素上。

  //从当前vnode开始，向下追溯
  while (isDef(childNode.componentInstance)) {
    childNode = childNode.componentInstance._vnode
    if (childNode.data) {
      data = mergeClassData(childNode.data, data)
    }
  }

  //从当前vnode开始，向上追溯
  while (isDef(parentNode = parentNode.parent)) {
    if (parentNode.data) {
      data = mergeClassData(data, parentNode.data)
    }
  }

  return renderClass(data.staticClass, data.class)
}

function mergeClassData(child: VNodeData, parent: VNodeData): {
  staticClass: string,
  class: any
} {
  return {
    //staticClass直接通过concat方法将child和parent对应的staticClass进行字符串拼接
    staticClass: concat(child.staticClass, parent.staticClass),
    class: isDef(child.class)
      ? [child.class, parent.class]
      : parent.class
  }
}

//先将动态class转换为字符串，然后将静态class字符串与转换后的动态class字符串进行拼接
export function renderClass(staticClass: ?string,
                            dynamicClass: any): string {
  if (isDef(staticClass) || isDef(dynamicClass)) {
    return concat(staticClass, stringifyClass(dynamicClass))
  }
  /* istanbul ignore next */
  return ''
}

//该方法用于将可能存在的a字符串与b字符串进行拼接
export function concat(a: ?string, b: ?string): string {
  return a ? b ? (a + ' ' + b) : a : (b || '')
}

/**
 * 注意，这里的stringifyClass、stringifyArray和stringifyObject三个方法，
 * 就是对应于动态class可以使用的值的数据类型，可以使用的值的类型为：
 * :class="hello"
 * 1）字符串：let hello = 'hello'
 * 2）对象：let hello = {hello:true, world:true}
 * 3）字符串数组：let hello = ['hello', 'world']
 * 4）对象数组：let hello = [{hello:true}, {world:true}]
 * 5）字符串对象数组：let hello = ['hello', {world:true}]
 */

export function stringifyClass(value: any): string {
  if (Array.isArray(value)) {
    return stringifyArray(value)
  }
  if (isObject(value)) {
    return stringifyObject(value)
  }
  if (typeof value === 'string') {
    return value
  }
  /* istanbul ignore next */
  return ''
}

/**
 * 如官方文档介绍，:class形式的动态绑定可以设置数组类型
 * 支持:class="['hello', 'world']"和:class="[{hello:true}, {world:true}]"形式的赋值
 * @param value
 * @returns {string}
 */
function stringifyArray(value: Array<any>): string {
  let res = ''
  let stringified
  for (let i = 0, l = value.length; i < l; i++) {
    if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
      if (res) res += ' '
      res += stringified
    }
  }
  return res
}

//支持:class="{hello:true}"形式的赋值
function stringifyObject(value: Object): string {
  let res = ''
  for (const key in value) {
    if (value[key]) {
      if (res) res += ' '
      res += key
    }
  }
  return res
}
